﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Reflection;
using System.Web;
using TCC.CMFlow.Contexts;
using TCC.CMFlow.Models;
using TCC.CMFlow.Models.Enumerators;
using TCC.CMFlow.Properties;
using TCC.WSClient;

namespace TCC.CMFlow.Business
{
    public class WorkflowBusiness
    {
        private DataContext db = new DataContext();

        public void ProcessarWorkflowsETL()
        {
            try
            {
                object metadata = null;

                var categoriasWorkflow = db.CategoriaWorkflow.ToList();

                foreach (var categoria in categoriasWorkflow)
                {
                    if (categoria.CategoriaWebService == null || string.IsNullOrEmpty(categoria.CategoriaWebService.WebServiceUri))
                    {
                        continue;
                    }

                    WebServiceInvoker ws = new WebServiceInvoker(new Uri(categoria.CategoriaWebService.WebServiceUri));
                    metadata = ws.InvokeMethod<object>(categoria.CategoriaWebService.ServicoNome, categoria.CategoriaWebService.OperacaoNome);

                    var webServiceDados = RetornaWebServiceDados(metadata, categoria.CategoriaWebService.CategoriaWebServiceAtributos.ToList());
                    var atributosChave = categoria.CategoriaWebService.CategoriaWebServiceAtributos
                        .Where(a => a.IsPrimaryKey)
                        .ToList();

                    //// Gera workflows
                    this.GerarWorkflows(webServiceDados, atributosChave, categoria.ID);

                    //// Reprocessa dados de workflows
                    this.ReprocessarWorkflows(categoria.Workflows.ToList(), webServiceDados, atributosChave);

                    Logger.AddLog(EnumTipoLog.Rotina, null, null, Resources.LogRotinaWorkflow);
                }
            }
            catch (Exception ex)
            {
                Logger.AddLog(EnumTipoLog.Erro, null, null, ex.Message);

                throw ex;
            }
        }

        private void GerarWorkflows(List<WorkflowDado> webServiceDados, List<CategoriaWebServiceAtributo> atributosChave, int categoriaWorkflowID)
        {
            if (atributosChave == null || !atributosChave.Any())
            {
                throw new Exception("Nenhuma chave primária definida para os atributos!");
            }

            var listaWorkflowDados = db.WorkflowDado.ToList();
            var workflowDadosChave = listaWorkflowDados
                .Where(d => atributosChave.Select(a => a.ID).Contains(d.ID))
                .ToList();

            var workflowDados = RetornaWorkflowDadosGerados(webServiceDados, workflowDadosChave);

            foreach (var dados in workflowDados)
            {
                var workflow = new Workflow();
                workflow.CategoriaWorkflowID = categoriaWorkflowID;
                workflow.DataEmissao = DateTime.Now;
                workflow.Status = (int)EnumStatusWorkflow.Gerado;

                db.Workflow.Add(workflow);
                db.SaveChanges();

                workflow.Dados = this.RetornaWorkflowDadosPreparado(new List<WorkflowDado>() { dados }, workflow.ID);
                if (workflow.Aprovacoes == null)
                {
                    workflow.Aprovacoes = new List<WorkflowAprovacao>();
                }
                workflow.Aprovacoes.Add(
                    new WorkflowAprovacao()
                    {
                        WorkflowID = workflow.ID,
                        DataHoraAprovacao = DateTime.Now,
                        Status = (int)EnumStatusWorkflow.Gerado,
                        Observacao = "Gerado automaticamente pelo sistema."
                    }
                );

                db.Entry(workflow).State = EntityState.Modified;
                db.SaveChanges();
            }
        }

        public void ReprocessarWorkflows(List<Workflow> workflows, List<WorkflowDado> webServiceDados, List<CategoriaWebServiceAtributo> atributosChave)
        {
            var workflowsReprocessamento = workflows
                .Where(w => w.Aprovacoes != null && w.Aprovacoes.Any() &&
                            w.Aprovacoes.LastOrDefault().Status == (int)EnumStatusWorkflow.Reprocessamento)
                .ToList();

            foreach (var w in workflowsReprocessamento)
            {
                var workflowDadosChave = w.Dados.Where(d => atributosChave.Select(a => a.ID).Contains(d.ID)).ToList();
                var workflowDados = RetornaWorkflowDadosReprocessamento(webServiceDados, workflowDadosChave, w.ID);

                var workflow = db.Workflow.Find(w.ID);

                //// Gera ação de reprocessado
                workflow.Aprovacoes.Add(
                    new WorkflowAprovacao
                    {
                        WorkflowID = w.ID,
                        DataHoraAprovacao = DateTime.Now,
                        Status = (int)EnumStatusWorkflow.Reprocessado,
                        Observacao = "Reprocessado automaticamente pelo sistema."
                    }
                );

                //// Atualiza os dados
                workflow.Dados.Clear();
                workflow.Dados = workflowDados;
                workflow.Status = (int)EnumStatusWorkflow.Reprocessado;
                db.Entry(workflow).State = EntityState.Modified;
                db.SaveChanges();
            }
        }

        private List<WorkflowDado> RetornaWebServiceDados(object metadata, List<CategoriaWebServiceAtributo> categoriaWebServiceAtributos)
        {
            List<WorkflowDado> workflowDados = new List<WorkflowDado>();

            if (metadata == null)
            {
                return workflowDados;
            }

            object myobject;
            if (metadata.GetType().IsArray)
            {
                var myarray = metadata as Array;
                foreach (var item in myarray)
                {
                    myobject = item;

                    var parentReference = categoriaWebServiceAtributos
                        .Where(c => c.Nome == myobject.GetType().Name).FirstOrDefault();

                    if (parentReference != null)
                    {
                        WorkflowDado workflowDado = new WorkflowDado();
                        workflowDado.ID = parentReference.ID;
                        workflowDado.NomeAtributo = parentReference.NomeExibicao;
                        workflowDado.Childrens = this.RetornaWebServiceDados(myobject, parentReference.Childrens);

                        workflowDados.Add(workflowDado);
                    }
                }

                return workflowDados;
            }
            else
            {
                myobject = metadata;
            }

            Type theType = myobject.GetType();
            var propriedadesMetadata = (from prop in theType.GetProperties()
                                        join atributo in categoriaWebServiceAtributos
                                          on prop.Name equals atributo.Nome
                                        select new
                                        {
                                            propInfo = prop,
                                            atributoID = atributo.ID,
                                            parentID = atributo.ParentID,
                                            nomeAtributo = atributo.NomeExibicao,
                                            ordem = atributo.Ordem,
                                            childrens = atributo.Childrens
                                        }).ToList();

            foreach (var p in propriedadesMetadata)
            {
                WorkflowDado workflowDado = new WorkflowDado();
                workflowDado.ID = p.atributoID;
                workflowDado.ParentID = p.parentID;
                workflowDado.NomeAtributo = p.nomeAtributo;
                workflowDado.Ordem = p.ordem;

                object propValue = p.propInfo.GetValue(myobject);

                if (p.propInfo.PropertyType.IsValueType || p.propInfo.PropertyType == typeof(String))
                {
                    workflowDado.ValorAtributo = (propValue != null ? propValue.ToString() : null);
                }
                else
                {
                    workflowDado.Childrens = this.RetornaWebServiceDados(propValue, p.childrens);
                }

                workflowDados.Add(workflowDado);
            }

            return workflowDados;
        }

        private List<WorkflowDado> RetornaWorkflowDadosGerados(List<WorkflowDado> webServiceDados, List<WorkflowDado> workflowDadosChave)
        {
            var workflowDados = new List<WorkflowDado>();

            foreach (var dado in webServiceDados)
            {
                var isRegistroEncontrado = false;
                
                foreach (var chaves in workflowDadosChave.GroupBy(g => g.WorkflowID))
                {
                    var isChaveEncontrada = true;

                    foreach (var chave in chaves.ToList())
                    {
                        var workflowDadosEncontrados = RetornaWorkflowDadosByChave(new List<WorkflowDado>() { dado }, chave);
                        if (!workflowDadosEncontrados.Any())
                        {
                            isChaveEncontrada = false;
                            break;
                        }
                    }

                    if (isChaveEncontrada)
                    {
                        isRegistroEncontrado = true;
                    };
                }

                if (!isRegistroEncontrado)
                {
                    workflowDados.Add(dado);
                }
            }

            return workflowDados;
        }

        private List<WorkflowDado> RetornaWorkflowDadosReprocessamento(List<WorkflowDado> webServiceDados, List<WorkflowDado> workflowDadosChave, int workflowID)
        {
            if (workflowDadosChave == null || !workflowDadosChave.Any())
            {
                throw new Exception("Nenhuma chave primária definida para os atributos!");
            }
            
            var workflowDados = new List<WorkflowDado>();

            foreach (var dado in webServiceDados)
	        {
                var isChaveEncontrada = true;

                foreach (var chave in workflowDadosChave)
                {
                    var workflowDadosEncontrados = RetornaWorkflowDadosByChave(new List<WorkflowDado>() { dado }, chave);
                    if(!workflowDadosEncontrados.Any())
                    {
                        isChaveEncontrada = false;
                        break;
                    }
                }

                if (isChaveEncontrada)
                {
                    workflowDados.Add(dado);
                }
	        }

            return this.RetornaWorkflowDadosPreparado(workflowDados, workflowID);
        }

        private List<WorkflowDado> RetornaWorkflowDadosByChave(List<WorkflowDado> webServiceDados, WorkflowDado workflowDadoChave)
        {
            var workflowDados = webServiceDados
                .Where(w => (workflowDadoChave.ID == w.ID && workflowDadoChave.ValorAtributo == w.ValorAtributo) ||
                            (w.Childrens != null && RetornaWorkflowDadosByChave(w.Childrens.ToList(), workflowDadoChave).Any())

                ).ToList();

            return workflowDados;
        }

        private List<WorkflowDado> RetornaWorkflowDadosPreparado(List<WorkflowDado> workflowDados, int workflowID)
        {
            if (workflowDados == null)
            {
                return null;
            }

            return (from dado in workflowDados
                    select new WorkflowDado
                    {
                        ID = dado.ID,
                        WorkflowID = workflowID,
                        ParentID = dado.ParentID,
                        NomeAtributo = dado.NomeAtributo,
                        ValorAtributo = dado.ValorAtributo,
                        Ordem = dado.Ordem,
                        Parent = dado.Parent,
                        Childrens = this.RetornaWorkflowDadosPreparado(dado.Childrens, workflowID)
                    }).ToList();
        }
    }
}